package com.zhangtao.demo.config.redis.jedis;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.alibaba.fastjson.JSONObject;
import com.zhangtao.demo.common.dto.Holder;
import com.zhangtao.demo.common.enums.RedisInfo;
import com.zhangtao.demo.common.util.RedisUtils;
import com.zhangtao.demo.common.util.ThrowableUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;

public class RedisPool {

	private static final Logger logger = LogManager.getLogger(RedisPool.class);
	private JedisPool pool;

	/**
	 * redis服务器地址
	 */
	private String host;
	/**
	 * redis服务器端口
	 */
	private int port;
	/**
	 * redis连接密码
	 */
	private String password;
	/**
	 * 当前选择的数据库
	 */
	private int currentDb;
	private int timeout = 10000;
	private final String CLIENT_NAME = "ztpersonalCommonUtil";
	/**
	 * redis服务信息：每次切换主机时获取所有的服务信息并存储下来
	 */
	private final Map<String, JSONObject> serverInfoM = new HashMap<>();

	public RedisPool(String host, int port, String password, int db) {
		this.host = host;
		this.port = port;
		this.password = password;
		this.currentDb = db;
	}

	public int getCurrentDb() {
		return currentDb;
	}

	public boolean setCurrentDb(int currentDb) {
		this.currentDb = currentDb;
		return true;
	}

	/**
	 * @Title: changeRedisPool
	 * @Description: 修改host和port
	 * @param: @param host
	 * @param: @param port
	 * @param: @param password
	 * @param: @param db
	 * @return: void
	 * @throws @author 张涛
	 * @Date 2018年11月30日 下午1:37:01
	 */
	public boolean changeRedisPool(String host, int port, String password, int db) {
		String oldHost = this.host;
		int oldPort = this.port;
		String oldPassword = this.password;
		int oldCurrentDb = this.currentDb;
		this.host = host;
		this.port = port;
		this.password = password;
		this.currentDb = db;
		try {
			this.connect();
			refreshRedisInfo();
			return true;
		} catch (Exception e) {
			logger.error("change host failed : {}", ThrowableUtil.getErrorInfoFromThrowable(e));
		}
		this.host = oldHost;
		this.port = oldPort;
		this.password = oldPassword;
		this.currentDb = oldCurrentDb;
		try {
			this.connect();
			refreshRedisInfo();
		} catch (Exception e) {
			logger.error("rollback to oldhost failed : {}", ThrowableUtil.getErrorInfoFromThrowable(e));
		}
		return false;
	}

	/**
	 * @Title: connect
	 * @Description: 连接
	 * @param:
	 * @return: void
	 * @throws @author 张涛
	 * @Date 2018年11月30日 下午1:37:21
	 */
	private void connect() {
		if (this.pool != null && !this.pool.isClosed()) {
			this.pool.close();
		}
		// this.pool = new JedisPool(host, port);
		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
		poolConfig.setMaxTotal(10);
		poolConfig.setMaxIdle(10);
		poolConfig.setMinIdle(2);
		poolConfig.setMaxWaitMillis(15000);
		if (StringUtils.isNotBlank(this.password)) {
			this.pool = new JedisPool(poolConfig, this.host, this.port, timeout, this.password);
		} else {
			this.pool = new JedisPool(poolConfig, this.host, this.port, timeout);
		}
	}

	/**
	 * 
	 * @Title: execute
	 * @Description: 使用Jedis操作redis
	 * @param: @param caller
	 * @return: void
	 * @throws @author 张涛
	 * @Date 2018年11月30日 下午1:34:45
	 */
	public void execute(CallWithJedis caller) {
		if (this.pool == null || this.pool.isClosed()) {
			this.connect();
		}
		Jedis jedis = pool.getResource();
		refreshDbAndAuth(jedis);
		try {
			caller.call(jedis);
		} catch (JedisConnectionException e) {
			caller.call(jedis); // 重试一次
		} finally {
			jedis.close();
		}
	}

	/**
	 * 
	 * @Title: executeMulti
	 * @Description: 使用transaction操作redis
	 * @param: @param caller
	 * @return: void
	 * @throws @author 张涛
	 * @Date 2018年11月30日 下午1:35:09
	 */
	public void executeMulti(CallWithTransaction caller) {
		if (this.pool == null || this.pool.isClosed()) {
			this.connect();
		}
		Transaction t = null;
		try (Jedis jedis = pool.getResource()) {
			refreshDbAndAuth(jedis);
			t = jedis.multi();
			caller.call(t);
		} catch (JedisConnectionException e) {
			caller.call(t); // 重试一次
		} finally {
			try {
				assert t != null;
				t.close();
			} catch (IOException e) {
				logger.error("close transaction failed:{}", ThrowableUtil.getErrorInfoFromThrowable(e));
			}
		}
	}

	/**
	 * 
	 * @Title: executePipelined
	 * @Description: 使用Pipelined操作redis
	 * @param: @param caller
	 * @return: void
	 * @throws @author 张涛
	 * @Date 2018年11月30日 下午1:35:25
	 */
	public void executePipelined(CallWithPipelined caller) {
		if (this.pool == null || this.pool.isClosed()) {
			this.connect();
		}
		Jedis jedis = pool.getResource();
		refreshDbAndAuth(jedis);
		Pipeline p = jedis.pipelined();
		try {
			caller.call(p);
		} catch (JedisConnectionException e) {
			caller.call(p); // 重试一次
		} finally {
			try {
				p.close();
			} catch (IOException e) {
				logger.error("close Pipeline failed:{}", ThrowableUtil.getErrorInfoFromThrowable(e));
			}
			jedis.close();
		}
	}

	private void refreshDbAndAuth(Jedis jedis) {
		if (jedis == null || !jedis.isConnected()) {
			return;
		}
		if (this.currentDb != 0) {
			String r = null;
			try {
				r = jedis.select(this.currentDb);
				jedis.clientSetname(CLIENT_NAME);
			} catch (JedisConnectionException e) {
				jedis = this.pool.getResource();
				r = jedis.select(this.currentDb);
			}
			logger.debug("select {} : [{}]", this.currentDb, r);
		}
	}

	public void refreshRedisInfo() {
		execute(j -> {
			j.clientSetname(CLIENT_NAME);
			String serverInfo = j.info();
			serverInfoM.clear();
			String[] infos = StringUtils.split(serverInfo, RedisUtils.COMMOND_END_STR);
			int infosAmount = infos.length;
			Holder<JSONObject> lastItemInfo = new Holder<JSONObject>();
			Holder<String> lastItem = new Holder<String>();
			for (int i = 0; i < infosAmount; i++) {
				String infoitem = infos[i];
				if (StringUtils.startsWith(infoitem, RedisUtils.SERVER_INFO_START)) {
					if (lastItem.value() != null) {
						serverInfoM.put(lastItem.value(), lastItemInfo.value());
					}
					lastItemInfo.value(new JSONObject());
					lastItem.value(infoitem.substring(1).trim());
				}
				int spliteIndex = infoitem.indexOf(":");
				if (spliteIndex > 0) {
					lastItemInfo.value().put(infoitem.substring(0, spliteIndex).trim(),
							infoitem.substring(spliteIndex + 1).trim());
				}
				if (i == (infosAmount - 1)) {
					serverInfoM.put(lastItem.value(), lastItemInfo.value());
				}
			}
		});
		logger.info("redis server [{}:{}] infor : {}", this.host, this.port, JSONObject.toJSONString(serverInfoM));
	}

	public JSONObject getRedisInfo(RedisInfo section) {
		return serverInfoM.get(section.name());
	}

}
